﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using FluxJpeg.Core;
using FluxJpeg.Core.Encoder;
using VideoChatApp.ChatServer;
using System.ComponentModel;
using System.Windows.Media.Imaging;
using System.IO;
using System.Threading;
namespace VideoChatApp
{
    public partial class Chat : UserControl
    {
        public Chat()
        {
            InitializeComponent();
        }

        private void Join_Click(object sender, RoutedEventArgs e)
        {

            this.ChatRoomName.IsEnabled =
                this.Join.IsEnabled = false;
            this.ChatText.IsEnabled = true;
            this.TextChatArea.Items.Clear();

            //this.client = new PubSubClient("PollingDuplex_IPubSub");
            //this.client = new PubSubClient("NetTcpBinding_IPubSub");
            //this.client = new ServerClient("NetTcpBinding_IServer");
            this.client = new ServerClient();
            this.client.RecieveTextReceived +=
                new EventHandler<RecieveTextReceivedEventArgs>(client_ReceiveTextReceived);
            this.client.PublishTextCompleted +=
                new EventHandler<AsyncCompletedEventArgs>(client_PublishTextCompleted);

            bool clientIsVideoSender = PrepareForVideoCapture();
            CreateLayout(clientIsVideoSender);

            if (clientIsVideoSender)
            {
                this.client.PublishVideoCompleted +=
                    new EventHandler<AsyncCompletedEventArgs>(client_PublishVideoCompleted);
            }
            else
            {
                this.client.RecieveVideoReceived +=
                    new EventHandler<RecieveVideoReceivedEventArgs>(client_ReceiveVideoReceived);
            }

            this.client.SubscribeAsync(this.ChatRoomName.Text);

            this.AddNotification("Joined " +
                this.ChatRoomName.Text, Colors.Gray);
        }


        void client_ReceiveVideoReceived(object sender,RecieveVideoReceivedEventArgs e)
        {
            if (!this.IsError(e))
            {
                if (e.content.Height == Constants.VideoHeight &&
                    e.content.Width == Constants.VideoWidth)
                {
                    lock (syncRoot)
                    {
                        frameBuffer.Add(e.content.Pixel);
                    }
                }

            }
        }

        void client_ReceiveTextReceived(object sender,RecieveTextReceivedEventArgs e)
        {
            if (!this.IsError(e))
            {
                this.AddNotification(e.content, Colors.Red);
                MessageBox.Show(e.content);
            }
        }

        void client_PublishVideoCompleted(object sender, AsyncCompletedEventArgs e)
        {
            this.IsError(e);
        }


        void client_PublishTextCompleted(object sender, AsyncCompletedEventArgs e)
        {
            if (!this.IsError(e))
            {
                this.AddNotification(e.UserState as string, Colors.Black);
            }
        }

        private void ChatText_KeyDown(object sender, KeyEventArgs e)
        {
            if (Key.Enter == e.Key)
            {
                this.client.PublishTextAsync(this.ChatRoomName.Text, this.ChatText.Text, this.ChatText.Text);
                
            }
        }


        #region // Helper methods

        object syncRoot = new Object();
        ServerClient client = null;
        CaptureSource cs = null;
        Storyboard captureTimer = null;
        byte[][,] raster;
        List<byte[]> frameBuffer;
        BackgroundWorker worker;

        private bool PrepareForVideoCapture()
        {
            if (CaptureDeviceConfiguration.AllowedDeviceAccess || CaptureDeviceConfiguration.RequestDeviceAccess())
            {
                VideoCaptureDevice vcd = CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();
                if (vcd != null)
                {
                    vcd.DesiredFormat = vcd.SupportedFormats.First<VideoFormat>(
                    (x) => x.PixelWidth == Constants.VideoWidth && x.PixelHeight == Constants.VideoHeight);

                    cs = new CaptureSource();
                    cs.CaptureImageCompleted += new EventHandler<CaptureImageCompletedEventArgs>(cs_CaptureImageCompleted);
                    cs.VideoCaptureDevice = vcd;

                    // This seems like the only way to see if the webcam is in use
                    try
                    {
                        cs.Start();
                    }
                    catch
                    {
                        cs = null;
                        return false;
                    }

                    captureTimer = new Storyboard();
                    captureTimer.Completed += new EventHandler(captureTimer_Completed);
                    captureTimer.Duration = TimeSpan.FromSeconds(1 / Constants.FramesPerSecond);
                    captureTimer.Begin();

                    return true;
                }
            }

            return false;
        }

        private void CreateLayout(bool clientIsVideoSender)
        {
            CamSpace.Width = Constants.VideoWidth;
            CamSpace.Height = Constants.VideoHeight;

            if (clientIsVideoSender)
            {
                VideoBrush vb = new VideoBrush();
                vb.SetSource(cs);
                CamSpace.Fill = vb;

                CamSpace.Stroke = new SolidColorBrush(Colors.Red);

                raster = FluxJpeg.Core.Image.CreateRaster(Constants.VideoWidth, Constants.VideoHeight, 3);
            }
            else
            {
                CamSpace.Fill = new ImageBrush { ImageSource = new BitmapImage() };
                
                // All frames get deposited into a buffer which 
                // is then played back at the appropriate FramesPerSecond
                frameBuffer = new List<byte[]>();
                worker = new BackgroundWorker();
                worker.DoWork += new DoWorkEventHandler(worker_DoWork);
                worker.RunWorkerAsync();
            }

        }

        void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            while (true)
            {

                this.Dispatcher.BeginInvoke(delegate
                {
                    lock (syncRoot)
                    {
                        if (frameBuffer.Count > 0)
                        {
                            ((CamSpace.Fill as ImageBrush).ImageSource as BitmapImage).SetSource(
                                new MemoryStream(frameBuffer[0]));
                            frameBuffer.RemoveAt(0);
                        }
                    }
                });

                Thread.Sleep(1000 / Constants.FramesPerSecond);
            }
        }

        void captureTimer_Completed(object sender, EventArgs e)
        {
            cs.CaptureImageAsync();
            captureTimer.Begin();
        }

        private FluxJpeg.Core.Image WriteableBitmapPixelsToImage(WriteableBitmap bitmap)
        {
            for (int i = 0; i < bitmap.PixelHeight; i++)
            {
                for (int j = 0; j < bitmap.PixelWidth; j++)
                {
                    int pixel = bitmap.Pixels[i * bitmap.PixelWidth + j];
                    raster[0][j, i] = (byte)(pixel >> 16);
                    raster[1][j, i] = (byte)(pixel >> 8);
                    raster[2][j, i] = (byte)pixel; 
                }
            }

            return new FluxJpeg.Core.Image(new ColorModel { colorspace = ColorSpace.RGB }, raster);
        }

        void cs_CaptureImageCompleted(object sender, CaptureImageCompletedEventArgs e)
        {
            MemoryStream ms = new MemoryStream();
            new JpegEncoder(WriteableBitmapPixelsToImage(e.Result),Constants.JpegQuality, ms).Encode();
            ms.Seek(0, SeekOrigin.Begin);
            VideoFrame vf = new VideoFrame { Height = Constants.VideoHeight, Width = Constants.VideoWidth };
            vf.Pixel = new byte[ms.Length];
            ms.Read(vf.Pixel, 0, (int)ms.Length);
            client.PublishVideoAsync(this.ChatRoomName.Text, vf);
        }


        bool IsError(AsyncCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                this.client.CloseAsync();
                this.client = null;
                this.AddNotification("ERROR: " + e.Error.Message, Colors.Gray);
                this.ChatRoomName.IsEnabled = this.Join.IsEnabled = true;
                this.ChatText.IsEnabled = false;

                // Clean up
                cs.Stop();
                cs = null;
                CamSpace.Fill = null;
                CamSpace.Stroke = null;
                raster = null;
                worker = null;
                frameBuffer = null;
            }

            return e.Error != null;
        }

        private void AddNotification(string text, Color c)
        {
            this.TextChatArea.Items.Add(text);
            this.TextChatArea.SelectedIndex = this.TextChatArea.Items.Count - 1;
            this.TextChatArea.UpdateLayout();
            this.TextChatArea.ScrollIntoView(this.TextChatArea.SelectedItem);
            ListBoxItem lbi = (ListBoxItem)(this.TextChatArea.ItemContainerGenerator.ContainerFromItem(
                this.TextChatArea.SelectedItem));
            lbi.Foreground = new SolidColorBrush(c);
        }

        #endregion
    }
}
